package org.example;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.util.SafeEncoder;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class RedisStatement implements Statement {
    private static final Set<String> commandSet = Arrays.stream(Protocol.Command.values()).map(Enum::name).collect(Collectors.toSet());

    private final RedisConnection connection;
    private final Jedis redisClient;

    private ResultSet resultSet;
    private boolean isClosed = false;
    private int fetchSize = 1;

    public RedisStatement(RedisConnection connection, Jedis redisClient) {
        this.connection = connection;
        this.redisClient = redisClient;
    }

    public String[] sendCommand(String sql) throws SQLException {
        sql = sql.trim();
        Protocol.Command command = null;
        for (String commandStr : commandSet) {
            if(sql.toLowerCase().startsWith(commandStr.toLowerCase())){
                command = Protocol.Command.valueOf(commandStr);
                sql = sql.substring(commandStr.length());
            }
        }
        sql = sql.replaceAll("\\s+"," ").trim();

        Object obj = redisClient.sendCommand(command, sql.split(" "));
        String[] result = this.decodeResult(obj);
       return result;
    }
    private String[] decodeResult(Object originResult) throws SQLException {
        String[] decodedResult;
        if (originResult == null) {
            decodedResult = new String[]{null};
        } else if (originResult.getClass().isArray()) {
            String decoded = SafeEncoder.encode((byte[]) originResult);
            decodedResult = Stream.of(decoded)
                    .toArray(String[]::new);
        } else if (originResult instanceof Collection) {
            List<byte[]> convertedList = Utils.convert((Collection<?>) originResult, new ArrayList<>());
            decodedResult = convertedList.stream()
                    .map(SafeEncoder::encode)
                    .toArray(String[]::new);
        } else {

            decodedResult = Stream.of(originResult.toString())
                    .toArray(String[]::new);
        }

        return decodedResult;
    }

    public static void main(String[] args) throws SQLException {
        RedisConnection redisConnection = new RedisConnection("jdbc:redis://127.0.0.1:6379/0", null);
        RedisStatement redisStatement = new RedisStatement(redisConnection, redisConnection.getConnection());
        String[] get_as = redisStatement.sendCommand("get a");
        System.out.println(get_as);
    }


    @Override
    public ResultSet executeQuery(String sql) throws SQLException {

        this.checkClosed();

        String[] result = this.sendCommand(sql);
        return new RedisResultSet(result, this);
    }


    @Override
    public int executeUpdate(String sql) throws SQLException {
        this.checkClosed();

        String[] result = this.sendCommand(sql);
        return result.length;
    }

    @Override
    public void close() throws SQLException {
        if (isClosed) {
            return;
        }
        if (this.resultSet != null && !this.resultSet.isClosed()) {
            this.resultSet.close();
        }
        isClosed = true;
    }

    @Override
    public int getMaxFieldSize() throws SQLException {
        this.checkClosed();
        return Integer.MAX_VALUE;
    }

    @Override
    public void setMaxFieldSize(int max) throws SQLException {
        this.checkClosed();
        // do nothing
    }

    @Override
    public int getMaxRows() throws SQLException {
        this.checkClosed();
        return 0;
    }

    @Override
    public void setMaxRows(int max) throws SQLException {
        this.checkClosed();
        // do nothing
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
        this.checkClosed();
        // do nothing
    }

    @Override
    public int getQueryTimeout() throws SQLException {
        this.checkClosed();
        return 0;
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
    }

    @Override
    public void cancel() throws SQLException {
        throw new SQLFeatureNotSupportedException("cancel not implemented");
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {
        this.checkClosed();
    }

    @Override
    public void setCursorName(String name) throws SQLException {
        throw new SQLFeatureNotSupportedException("setCursorName not implemented");
    }

    @Override
    public boolean execute(String sql) throws SQLException {
        this.checkClosed();

        String[] result = this.sendCommand(sql);
        this.resultSet = new RedisResultSet(result, this);

        return true;
    }

    @Override
    public ResultSet getResultSet() throws SQLException {
        this.checkClosed();
        return this.resultSet;
    }

    @Override
    public int getUpdateCount() throws SQLException {
        this.checkClosed();
        // 原因在父类
        return -1;
    }

    @Override
    public boolean getMoreResults() throws SQLException {
        this.checkClosed();
        return this.getMoreResults(CLOSE_CURRENT_RESULT);
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        this.checkClosed();
        throw new SQLFeatureNotSupportedException("setFetchDirection not implemented");
    }

    @Override
    public int getFetchDirection() throws SQLException {
        this.checkClosed();
        return ResultSet.FETCH_FORWARD;
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        this.checkClosed();
        this.fetchSize = rows;
    }

    @Override
    public int getFetchSize() throws SQLException {
        this.checkClosed();
        return this.fetchSize;
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        this.checkClosed();
        return ResultSet.CONCUR_READ_ONLY;
    }

    @Override
    public int getResultSetType() throws SQLException {
        this.checkClosed();
        return ResultSet.TYPE_FORWARD_ONLY;
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        this.checkClosed();
        throw new SQLFeatureNotSupportedException("addBatch not implemented");
    }

    @Override
    public void clearBatch() throws SQLException {
        this.checkClosed();
        throw new SQLFeatureNotSupportedException("addBatch not implemented");
    }

    @Override
    public int[] executeBatch() throws SQLException {
        this.checkClosed();
        throw new SQLFeatureNotSupportedException("executeBatch not implemented");
    }

    @Override
    public Connection getConnection() throws SQLException {
        this.checkClosed();
        return this.connection;
    }

    @Override
    public boolean getMoreResults(int current) throws SQLException {
        this.checkClosed();
        return false;
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        throw new SQLFeatureNotSupportedException("getGeneratedKeys not implemented");
    }

    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        this.executeUpdate(sql);
        return 0;
    }

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        return this.executeUpdate(sql, 0);
    }

    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        return this.executeUpdate(sql, 0);
    }

    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        this.executeUpdate(sql);
        return true;
    }

    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        return this.execute(sql, 0);
    }

    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        return this.execute(sql, 0);
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        this.checkClosed();
        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

    @Override
    public boolean isClosed() throws SQLException {
        return this.isClosed;
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        throw new SQLFeatureNotSupportedException("setPoolable not implemented");
    }

    @Override
    public boolean isPoolable() throws SQLException {
        this.checkClosed();
        return false;
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        this.checkClosed();
        throw new SQLFeatureNotSupportedException("closeOnCompletion not implemented");
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        this.checkClosed();
        return false;
    }

    private void checkClosed() throws SQLException {
        if (isClosed()) {
            throw new SQLException("Statement is closed.");
        }
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        try {
            return iface.cast(this);
        } catch (ClassCastException cce) {
            throw new SQLException("Unable to unwrap to " + iface);
        }
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this);
    }
}
